﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using Site3AI.Models;

namespace WeiXinService.Services
{

    public class WxServices : IDisposable
    {
        public class ApiResultRaw
        {
            public bool IsSuccess { get; set; }
            public DateTime CallingTime { get; set; }
            public string Result { get; set; }
        }
        public class ApiResult<T>
        {
            public ApiResultRaw Raw { get; set; }
            public T Result { get; set; }

        }
        public string AppId = "wx63fecf6c0ae4d57c";
        public string AppSecret = "df16eb944322ccbac10de2f955fdc40e";
        public DateTime AccessTokenExpireTime = DateTime.MinValue;
        public string AccessToken = null;
        System.Net.Http.HttpClient Cli;

        const string WEIXINTOKEN = "site3aitokenforweixin";
        const long tickoffsettoposixtimestamp = 621355968000000000;

        Lazy<SiteDbEntities> _db = new Lazy<SiteDbEntities>();
        public SiteDbEntities DB
        {
            get
            {
                return _db.Value;
            }
        }
        public WxServices()
        {
            Cli = new System.Net.Http.HttpClient();
            Cli.BaseAddress = new Uri("https://api.weixin.qq.com/cgi-bin/");
        }

        public string GetAccessToken()
        {
            var av = new { access_token = "", expires_in = 0 };
            var r = CallApi(string.Format( "token?grant_type=client_credential&appid={0}&secret={1}",AppId,AppSecret), av,
                null, () =>
                {
                    return (string.IsNullOrEmpty(AccessToken) && AccessTokenExpireTime > DateTime.Now);
                }
            );
            if (r != null && r.Raw.IsSuccess)
            {

                AccessToken = r.Result.access_token;
                AccessTokenExpireTime = r.Raw.CallingTime.AddSeconds(r.Result.expires_in);

            }
            return AccessToken;
        }

        public dynamic GetUerInfo(string useropenid)
        {
            var av = new
            {
                subscribe = 0,
                openid = "",
                nickname = "",
                sex = 0,
                language = "",
                city = "",
                province = "",
                country = "",
                headimgurl = "",
                subscribe_time = 0L
            };
            var r = CallApi(string.Format("user/info?access_token={0}&openid={1}&lang=zh_CN", GetAccessToken(), useropenid), av);
            //return r == null ? null : r.Result;
            return r;
        }

        public ApiResult<T> CallApi<T>(string q, T anonymousObj, Func<T, DateTime, T> procAfterCall = null, Func<bool> skipBeforeCall = null, int retytimes = 3, int retrypersec = 5)
        {
            var r = CallApiRaw(q, skipBeforeCall, retytimes, retrypersec);
            if (null == r) return null;
            if (!r.IsSuccess) return new ApiResult<T> { Raw = r, Result = default(T) };
            try
            {
                var rawobj = Newtonsoft.Json.JsonConvert.DeserializeAnonymousType(r.Result, anonymousObj);
                if (null == procAfterCall) return new ApiResult<T> { Raw = r, Result = rawobj };
                var newobj = procAfterCall.Invoke(rawobj, r.CallingTime);
                return new ApiResult<T> { Raw = r, Result = newobj };
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        public ApiResultRaw CallApiRaw(string q, Func<bool> skipBeforeCall = null, int retytimes = 3, int retrypersec = 5)
        {
            for (int i = 0; i < retytimes; i++)
            {
                try
                {
                    if (null != skipBeforeCall)
                    {
                        if (skipBeforeCall.Invoke()) return new ApiResultRaw { IsSuccess = false };
                    }
                    DateTime now = DateTime.Now;
                    var rst = Cli.GetStringAsync(q);
                    rst.Wait();
                    var rs = rst.Result;

                    return new ApiResultRaw { IsSuccess = true, Result = rs, CallingTime = now };
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    if (i < retytimes - 1)
                    {
                        new AutoResetEvent(false).WaitOne(retrypersec * 1000);
                    }
                    else
                    {
                        throw ex;
                    }
                }
            }
            return null;//never execute this
        }

        public bool VerifyWxSignature(string signature, string timestamp, string nonce)
        {
            string[] ks = new[] { WEIXINTOKEN, timestamp, nonce };
            Array.Sort<string>(ks);
            var sig = string.Empty;
            using (var sha1 = System.Security.Cryptography.SHA1.Create())
            {
                var s = sha1.ComputeHash(System.Text.Encoding.ASCII.GetBytes(string.Join("", ks)));
                sig = BitConverter.ToString(s).Replace("-", "");
            }
            var r = sig.Equals(signature, StringComparison.CurrentCultureIgnoreCase);
            return r;
        }
        public long ToPosixTime(DateTime dt)
        {
            return ((System.DateTime.Now.ToUniversalTime().Ticks - tickoffsettoposixtimestamp) / 10000);
        }
        public DateTime FromPosixTime(long timestamp)
        {
            return new DateTime(tickoffsettoposixtimestamp + timestamp * 10000);
        }


        public void Dispose()
        {
            if (_db.IsValueCreated) _db.Value.Dispose();
            if (Cli != null) Cli.Dispose(); 
            Cli = null;
        }
    }
}